 /**********************************************************************
 * Fichier : USBDrivaCore.c
 *
 * Résumé : Fichier source qui définit les fonctions "coeur"
 *          (internes) du module.
 *          Ces fonctions ne devraient jamais être utilisées sans
 *          protection d'accès.
 *
 * Auteur : Vincent Léger
 *
 * Dernière modification : Lundi 9 décembre 2012
 *
 * Note: Dans le cas du présent pilote, la capture du sémaphore
 **********************************************************************/
//En-tete
#include "USBDrivaCore.h"

/******************************************
*********DÉFINITIONS DE FONCTIONS**********
******************************************/
/****DEPRACATED --> Pas utilie si on configure bien le altsetting dans
*                   la fonction "probe".
*/
 /**********************************************************************
 *  Fonction : GetHostDescriptor
 *
 *  Résumé : Utilise pour retrouver un endpoint valide sur un altsetting
 *           qui correspond aux bonnes caractéristiques.
 *
 *  Entrées : - struct usb_interface* intf --> pointeur vers l'interface
 *                                             activé du module
 *
 *  Sorties : - aucune
 *
 *  Valeur de retour : struct usb_endpoint_descriptor*
 *                       --> NULL    = erreur
 *                       --> >0      = addresse du endpoint descriptor
 **********************************************************************/
/*struct usb_endpoint_descriptor* GetHostDescriptor(struct usb_interface* intf)
{
    struct usb_endpoint_descriptor *endpointDesc = NULL;
    int i = 0;
    int j = 0;

    if (intf != NULL)
    {
        DEBUG_VALUE("NUMBER OF DESCRIPTORS : ",intf->num_altsetting);
        if (intf->num_altsetting > 1)
        {
            for (i = 0; i < intf->num_altsetting; i++)
            {
                DEBUG_VALUE("PROBING DESCRIPTOR #",i);
                if (&intf->altsetting[i].desc!=NULL)
                    DEBUG_VALUE("alt setting found, descriptor type :",intf->altsetting[i].desc.bDescriptorType);
                if ((&intf->altsetting[i].desc!=NULL)&&
                        (intf->altsetting[i].desc.bDescriptorType == 4))
                {
                    DEBUG_VALUE("CLASS : ",intf->altsetting[i].desc.bInterfaceClass);
                    DEBUG_VALUE("SUB-CLASS : ",intf->altsetting[i].desc.bInterfaceSubClass);
                    DEBUG_VALUE("NUM ENDPOINTS: ",intf->altsetting[i].desc.bNumEndpoints);

                    for(j=0;j<intf->altsetting[i].desc.bNumEndpoints;j++)
                    {
                        endpointDesc = &intf->altsetting[i].endpoint[j].desc;
                        DEBUG_VALUE("endpoint descriptor #",j);
                        if (endpointDesc != NULL)
                        {
                            DEBUG_VALUE("NOT NULL !!! bDescriptorType = ", endpointDesc->bDescriptorType);
                            if (endpointDesc->bDescriptorType == 5)
                            {
                                DEBUG_MESSAGE("FOUND !!!");
                                    break;
                            }
                            else
                                endpointDesc = NULL;
                        }
                    }
                    if (endpointDesc != NULL)
                    {
                        DEBUG_MESSAGE("FOUND THE INTERFACE DESCRIPTOR !!!");
                        DEBUG_VALUE("ALTSETTING #",i);
                        DEBUG_VALUE("ENDPOINT #",j);
                        break;
                    }
                }
                endpointDesc = &intf->altsetting[i].endpoint->desc;
                if (endpointDesc != NULL)
                    DEBUG_VALUE("Descriptor type : ", intf->altsetting[i].desc.bDescriptorType);

                if ((endpointDesc != NULL)&&(intf->altsetting[i].desc.bDescriptorType == 5))
                    break;
            }
        }
        else
            endpointDesc = &intf->altsetting[0].endpoint->desc;

    }
    return endpointDesc;
}*/

 /**********************************************************************
 *  Fonction : initURB
 *
 *  Résumé : Fonction d'initialisation des URBs. Utilisé pour lancer une
 *           requête asynchrone.
 *
 *  Entrées : - struct usb_interface* intf --> pointeur vers l'interface
 *                                             video stream du device
 *
 *  Sorties : - aucune
 *
 *  Valeur de retour : int  --> 0       = succès
 *                          --> -ENODEV = ERREUR, pas de device trouvé
 *                          --> -EIO    = ERREUR, endpoint introuvable
 *                          --> -ENOMEM = ERREUR, manque de mémoire
 *                          --> autre (<0) = code d'erreur de submit_urb
 *
 *  note: Une grande partie du code de cette fonction est directement
 *        donnée dans le cahier de charges du laboratoire.
 **********************************************************************/
int initURB(struct usb_interface* intf)
{
    //declaration + initialisation des variables
    int size = 0;                       //taille du tampon de réception (pour chaque URB)
    int nbPackets = 0;                  //nombre de paquets asynchrones par URB
    u16 myPacketSize = 0;               //taille des paquets
    struct usbdriva_dev *MyDev = NULL;  //pointeur vers la structure personnelle
    struct usb_endpoint_descriptor *endpointDesc = NULL; //endpoint de l'interface
    unsigned int i = 0; //compteur de boucles principal
    unsigned int j = 0; //compteur de boucles internes
    int error = 0;      //controle d'erreurs

    DEBUG_MESSAGE("IN FUNCTION initURB");
    //on cherche la structure usager
    if (intf != NULL)
        MyDev = usb_get_intfdata(intf);
    if (MyDev == NULL)
        return -ENODEV;
    //retrouve le endpoint0 sur le "current altsetting" (le bon altsetting doit etre activé dans probe)
    endpointDesc = &intf->cur_altsetting->endpoint[0].desc;

    //on trouve le endpoint sur le bon altsetting: NON !!!
    //if (endpointDesc == NULL)
    //    endpointDesc = GetHostDescriptor(intf);
    if (endpointDesc == NULL)   //si pas bon... on abandonne tout espoir
        return -EIO;
    nbPackets = 40; //nombre de packets asynchrone par URB. (hard-codé... valeur donné)
    myPacketSize = le16_to_cpu(endpointDesc->wMaxPacketSize);   //déterminé par l'interface
    //grandeur du buffer --> nombre de paquets * grosseur des paquets
    size = myPacketSize * nbPackets;
    sentURB = 0;    //reset urb usage count
    for (i = 0; i < NB_URB; ++i) //pour chaque URB...
    {
        DEBUG_VALUE("INITIALIZING URB #",i);
        usb_free_urb(myUrb[i]); // Pour être certain
        myUrb[i] = usb_alloc_urb(nbPackets, GFP_KERNEL); //creation du URB
        if (myUrb[i] == NULL)
        {
            DEBUG_VALUE("Impossible de generer le URB #",i);
            return -ENOMEM;
        }
        DEBUG_MESSAGE("URB ALLOCATED");
        //allocation du tampon de réception
        myUrb[i]->transfer_buffer = usb_alloc_coherent(MyDev->udev,
                                            size,
                                            GFP_KERNEL,
                                            &myUrb[i]->transfer_dma);
        DEBUG_MESSAGE("Buffer Allocated");
        if (myUrb[i]->transfer_buffer == NULL)
        {
            DEBUG_VALUE("transfer buffer NULL pour le URB #",i);
            usb_free_urb(myUrb[i]);
            return -ENOMEM;
        }
        DEBUG_MESSAGE("TRANSFER BUFFER ALLOCATED");
        //configuration du URB
        myUrb[i]->dev = MyDev->udev;
        myUrb[i]->context = MyDev;      //on passe l'addresse de la structure perso
        //on trouve le canal à partir de l'addresse du endpoint 0
        myUrb[i]->pipe = usb_rcvisocpipe(MyDev->udev, endpointDesc->bEndpointAddress);
        myUrb[i]->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;  //NOW !!
        myUrb[i]->interval = endpointDesc->bInterval;
        myUrb[i]->complete = complete_callback;         //fonction de callback
        myUrb[i]->number_of_packets = nbPackets;
        myUrb[i]->transfer_buffer_length = size;
        DEBUG_MESSAGE("EVERYTHING SET");

        for (j = 0; j < nbPackets; ++j)//pour chaque paquet
        {   //initialisation index + taille
            myUrb[i]->iso_frame_desc[j].offset = j * myPacketSize;
            myUrb[i]->iso_frame_desc[j].length = myPacketSize;
        }
        DEBUG_VALUE("INIT COMPLETE FOR URB #",i);

    }
    DEBUG_MESSAGE("ALL URB INITIALIZED");
    for(i = 0; i < NB_URB; i++) //pour chaque URB
    {   //soumettre le URB
        error = usb_submit_urb(myUrb[i], GFP_KERNEL);
        if (error < 0)
        {
            DEBUG_VALUE("Erreur dans la soumission du URB #",i);
            DEBUG_VALUE("CODE D'ERREUR : ",error);
            //TODO: destroy dead URBs
            return error;
        }
        DEBUG_VALUE("SUBMITTED URB #",i);
    }
    DEBUG_MESSAGE("ALL URB SENT");
    return 0;
}

/**********************************************************************
*  Fonction : complete_callback
*
*  Résumé : Fonction de callback pour les URBs.
*
*  Entrées : - struct urb* urb --> URB en traitement
*
*  Sorties : - aucune
*
*  Valeur de retour : aucune (void)
*
*  note: Cette fonction est fournise dans le cahier de charges.
**********************************************************************/
static void complete_callback(struct urb *urb)
{
    //déclaration des variables
    int ret;                    //erreur dans le renvoi des URB
    int i;                      //compteur de boucles
    unsigned char * data;       //pointeur vers les données du paquet
    unsigned int len;           //taille des données
    unsigned int maxlen;        //maximum de caractères à transférer
    unsigned int nbytes;        //nombre de bytes transférés
    void * mem;                 //pointeur vers la donnée traitée
    struct usbdriva_dev *MyDev; //pointeur vers la structure personnelle
    //on recupere la structure personnelle
    MyDev = urb->context;

    DEBUG_MESSAGE("IN CALLBACK FUNCTION !!!");
    if(urb->status == 0)    //si le URB n'est pas encore traité
    {
        for (i = 0; i < urb->number_of_packets; ++i)
        {
            if(myStatus == 1)
            {
                continue;   //traitement déjà fini
            }
            if (urb->iso_frame_desc[i].status < 0)
            {
                continue;   //paquet sans données
            }
            //données du paquet courant
            data = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
            if(data[1] & (1 << 6))
            {
                continue;
            }
            //quantité de donnée dans le paquet
            len = urb->iso_frame_desc[i].actual_length;
            if (len < 2 || data[0] < 2 || data[0] > len)
            {
                continue;
            }
            //MAGIE !!
            //(prend le max de data selon ce qui est disponible et le max établi)
            len -= data[0];
            maxlen = myLength - myLengthUsed ;
            mem = myData + myLengthUsed;
            nbytes = min(len, maxlen);
            memcpy(mem, data + data[0], nbytes);
            myLengthUsed += nbytes;
            if (len > maxlen)   //reçu le max de données
            {
                myStatus = 1; // DONE
                DEBUG_VALUE("because of len>maxlen myStatus = ", myStatus);
            }
            /* Mark the buffer as done if the EOF marker is set. */
            if ((data[1] & (1 << 1)) && (myLengthUsed != 0))
            {
                myStatus = 1; // DONE
                DEBUG_VALUE("because of EOF marker is set myStatus =  ", myStatus);
            }
        }
        if (!(myStatus == 1))
        {
            //traitement pas complété... on re-soumet le URB
            if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0)
            {
                DEBUG_VALUE("Erreur dans submit URB code=",ret);
            }
            DEBUG_MESSAGE("URB RESUBMITED");
        }
        else    //URB traité avec succès
        {
            sentURB += 1;       //1 reçu de plus...
            DEBUG_VALUE("NUMBER OF RECEIVED URB : ",sentURB);
            myStatus = 0;       //re-initialise le status
            if(sentURB == 5)    //Si on a tout reçu...
            {
                DEBUG_VALUE("myLengthUsed : ", myLengthUsed );
                complete (&MyDev->callback);    //synchronisation avec le read
                sentURB = 0; //re-init le nombre de URB reçus
            }
        }
    }
    else
    {   //le URB est déjà traité... on le laisse mourir en paix
        DEBUG_MESSAGE("CALLBACK ERROR !!! (urb status not 0)");
    }
    DEBUG_MESSAGE("CALLBACK FINISHED");
    return;
}
